System.Span<T> – struktura

Tento článek obsahuje doplňující poznámky k referenční dokumentaci pro toto rozhraní API.

Typ Span<T> je struktura odkazu , která je přidělena v zásobníku, nikoli ve spravované haldě. Typy ref struktur mají řadu omezení, aby se zajistilo, že je nelze upřednostnět na spravovanou haldu, včetně toho, že není možné je zadávat do rámečku, nedají se přiřadit k proměnným typu nebo Objectdynamic k žádnému typu rozhraní, nemůžou být pole v referenčním typu a nelze je použít napříč await hranicemi a yield hranicemi. Kromě toho volání dvou metod Equals(Object) , a GetHashCode, vyvolat NotSupportedException.

Důležité

Vzhledem k tomu, že se jedná o typ pouze zásobníku, Span<T> není vhodný pro mnoho scénářů, které vyžadují ukládání odkazů na vyrovnávací paměti v haldě. To platí například u rutin, které provádí asynchronní volání metod. V takových scénářích můžete použít doplňkové System.Memory<T> typy a System.ReadOnlyMemory<T> typy.

Pro rozsahy, které představují neměnné nebo jen pro čtení struktury, použijte System.ReadOnlySpan<T>.

Memory (Paměť)

A Span<T> představuje souvislou oblast libovolné paměti. Instance Span<T> se často používá k uložení prvků pole nebo části pole. Na rozdíl od pole ale Span<T> instance může odkazovat na spravovanou paměť, nativní paměť nebo paměť spravovanou v zásobníku. Následující příklad vytvoří Span<Byte> z pole:

// Create a span over an array.
var array = new byte[100];
var arraySpan = new Span<byte>(array);

byte data = 0;
for (int ctr = 0; ctr < arraySpan.Length; ctr++)
    arraySpan[ctr] = data++;

int arraySum = 0;
foreach (var value in array)
    arraySum += value;

Console.WriteLine($"The sum is {arraySum}");
// Output:  The sum is 4950
// Create a span over an array.
let array = Array.zeroCreate<byte> 100
let arraySpan = Span<byte> array

let mutable data = 0uy
for i = 0 to arraySpan.Length - 1 do
    arraySpan[i] <- data
    data <- data + 1uy

let mutable arraySum = 0
for value in array do
    arraySum <- arraySum + int value

printfn $"The sum is {arraySum}"
// Output:  The sum is 4950

Následující příklad vytvoří Span<Byte> z 100 bajtů nativní paměti:

// Create a span from native memory.
var native = Marshal.AllocHGlobal(100);
Span<byte> nativeSpan;
unsafe
{
    nativeSpan = new Span<byte>(native.ToPointer(), 100);
}
byte data = 0;
for (int ctr = 0; ctr < nativeSpan.Length; ctr++)
    nativeSpan[ctr] = data++;

int nativeSum = 0;
foreach (var value in nativeSpan)
    nativeSum += value;

Console.WriteLine($"The sum is {nativeSum}");
Marshal.FreeHGlobal(native);
// Output:  The sum is 4950
// Create a span from native memory.
let native = Marshal.AllocHGlobal 100
let nativeSpan = Span<byte>(native.ToPointer(), 100)

let mutable data = 0uy
for i = 0 to nativeSpan.Length - 1 do
    nativeSpan[i] <- data
    data <- data + 1uy

let mutable nativeSum = 0
for value in nativeSpan do
    nativeSum <- nativeSum + int value

printfn $"The sum is {nativeSum}"
Marshal.FreeHGlobal native
// Output:  The sum is 4950

Následující příklad používá klíčové slovo stackalloc jazyka C# k přidělení 100 bajtů paměti v zásobníku:

// Create a span on the stack.
byte data = 0;
Span<byte> stackSpan = stackalloc byte[100];
for (int ctr = 0; ctr < stackSpan.Length; ctr++)
    stackSpan[ctr] = data++;

int stackSum = 0;
foreach (var value in stackSpan)
    stackSum += value;

Console.WriteLine($"The sum is {stackSum}");
// Output:  The sum is 4950
    // Create a span on the stack.
    let mutable data = 0uy
    let stackSpan = 
        let p = NativeInterop.NativePtr.stackalloc<byte> 100 |> NativeInterop.NativePtr.toVoidPtr
        Span<byte>(p, 100)

    for i = 0 to stackSpan.Length - 1 do
        stackSpan[i] <- data
        data <- data + 1uy

    let mutable stackSum = 0
    for value in stackSpan do
        stackSum <- stackSum + int value

    printfn $"The sum is {stackSum}"
// Output:  The sum is 4950

Vzhledem k tomu Span<T> , že je abstrakce nad libovolným blokem paměti, metody Span<T> typu a metod s Span<T> parametry pracují s libovolným Span<T> objektem bez ohledu na druh paměti, který zapouzdřuje. Například každý z samostatných částí kódu, které inicializují rozsah a vypočítají součet jeho prvků, lze změnit na jednu inicializaci a výpočetní metody, jak ukazuje následující příklad:

public static void WorkWithSpans()
{
    // Create a span over an array.
    var array = new byte[100];
    var arraySpan = new Span<byte>(array);

    InitializeSpan(arraySpan);
    Console.WriteLine($"The sum is {ComputeSum(arraySpan):N0}");

    // Create an array from native memory.
    var native = Marshal.AllocHGlobal(100);
    Span<byte> nativeSpan;
    unsafe
    {
        nativeSpan = new Span<byte>(native.ToPointer(), 100);
    }

    InitializeSpan(nativeSpan);
    Console.WriteLine($"The sum is {ComputeSum(nativeSpan):N0}");

    Marshal.FreeHGlobal(native);

    // Create a span on the stack.
    Span<byte> stackSpan = stackalloc byte[100];

    InitializeSpan(stackSpan);
    Console.WriteLine($"The sum is {ComputeSum(stackSpan):N0}");
}

public static void InitializeSpan(Span<byte> span)
{
    byte value = 0;
    for (int ctr = 0; ctr < span.Length; ctr++)
        span[ctr] = value++;
}

public static int ComputeSum(Span<byte> span)
{
    int sum = 0;
    foreach (var value in span)
        sum += value;

    return sum;
}
// The example displays the following output:
//    The sum is 4,950
//    The sum is 4,950
//    The sum is 4,950
open System
open System.Runtime.InteropServices
open FSharp.NativeInterop

// Package FSharp.NativeInterop.NativePtr.stackalloc for reuse.
let inline stackalloc<'a when 'a: unmanaged> length : Span<'a> =
    let voidPointer = NativePtr.stackalloc<'a> length |> NativePtr.toVoidPtr
    Span<'a>(voidPointer, length)

let initializeSpan (span: Span<byte>) =
    let mutable value = 0uy
    for i = 0 to span.Length - 1 do
        span[i] <- value
        value <- value + 1uy

let computeSum (span: Span<byte>) =
    let mutable sum = 0
    for value in span do
        sum <- sum + int value
    sum

let workWithSpans () =
    // Create a span over an array.
    let array = Array.zeroCreate<byte> 100
    let arraySpan = Span<byte> array

    initializeSpan arraySpan
    printfn $"The sum is {computeSum arraySpan:N0}"

    // Create an array from native memory.
    let native = Marshal.AllocHGlobal 100
    let nativeSpan = Span<byte>(native.ToPointer(), 100)

    initializeSpan nativeSpan
    printfn $"The sum is {computeSum nativeSpan:N0}"

    Marshal.FreeHGlobal native

    // Create a span on the stack.
    let stackSpan = stackalloc 100

    initializeSpan stackSpan
    printfn $"The sum is {computeSum stackSpan:N0}"

// The example displays the following output:
//    The sum is 4,950
//    The sum is 4,950
//    The sum is 4,950

Pole

Když zabalí matici, Span<T> může zabalit celou matici stejně jako v příkladech v části Paměť . Protože podporuje řezy, Span<T> může také odkazovat na libovolnou souvislou oblast v rámci pole.

Následující příklad vytvoří řez prostředních pěti prvků celočíselného pole 10 prvků. Všimněte si, že kód zdvojnásobí hodnoty každého celého čísla v řezu. Jak ukazuje výstup, změny provedené rozsahem se projeví v hodnotách pole.

using System;

var array = new int[] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
var slice = new Span<int>(array, 2, 5);
for (int ctr = 0; ctr < slice.Length; ctr++)
    slice[ctr] *= 2;

// Examine the original array values.
foreach (var value in array)
    Console.Write($"{value}  ");
Console.WriteLine();

// The example displays the following output:
//      2  4  12  16  20  24  28  16  18  20
module Program

open System

[<EntryPoint>]
let main _ =
    let array = [| 2; 4; 6; 8; 10; 12; 14; 16; 18; 20 |]
    let slice = Span<int>(array, 2, 5)
    for i = 0 to slice.Length - 1 do
        slice[i] <- slice[i] * 2

    // Examine the original array values.
    for value in array do
        printf $"{value}  "
    printfn ""
    0
// The example displays the following output:
//      2  4  12  16  20  24  28  16  18  20

Řezy

Span<T> obsahuje dvě přetížení Slice metody, která tvoří řez z aktuálního rozsahu, který začíná na zadaném indexu. To umožňuje zpracovávat data v Span<T> sadě logických bloků dat, které je možné zpracovat podle potřeby částmi kanálu zpracování dat s minimálním dopadem na výkon. Například vzhledem k tomu, že moderní serverové protokoly jsou často textové, je manipulace s řetězci a podřetězci obzvláště důležitá. String Ve třídě je hlavní metoda pro extrakci podřetěžců Substring. Pro datové kanály, které se spoléhají na rozsáhlou manipulaci s řetězci, nabízí jeho použití určité pokuty za výkon, protože:

  1. Vytvoří nový řetězec pro uložení podřetězce.
  2. Zkopíruje podmnožinu znaků z původního řetězce do nového řetězce.

Toto přidělení a operaci kopírování lze odstranit pomocí nebo Span<T>ReadOnlySpan<T>, jak ukazuje následující příklad:

using System;

class Program2
{
    static void Run()
    {
        string contentLength = "Content-Length: 132";
        var length = GetContentLength(contentLength.ToCharArray());
        Console.WriteLine($"Content length: {length}");
    }

    private static int GetContentLength(ReadOnlySpan<char> span)
    {
        var slice = span.Slice(16);
        return int.Parse(slice);
    }
}
// Output:
//      Content length: 132
module Program2

open System

let getContentLength (span: ReadOnlySpan<char>) =
    let slice = span.Slice 16
    Int32.Parse slice

let contentLength = "Content-Length: 132"
let length = getContentLength (contentLength.ToCharArray())
printfn $"Content length: {length}"
// Output:
//      Content length: 132